package com.abing.starry.service.impl;

import cn.hutool.json.JSONUtil;
import com.abing.sandbox.CodeSandbox;
import com.abing.sandbox.enums.ExecuteCodeStatusEnum;
import com.abing.sandbox.request.*;
import com.abing.starry.annotation.RedissonRateLimit;
import com.abing.starry.common.ErrorCode;
import com.abing.starry.exception.ThrowUtils;
import com.abing.starry.mapper.QuestionMapper;
import com.abing.starry.model.entity.QuestionEntity;
import com.abing.starry.model.entity.QuestionSubmitEntity;
import com.abing.starry.model.enums.QuestionStatusEnum;
import com.abing.starry.model.enums.QuestionSubmitLanguageEnum;
import com.abing.starry.model.enums.QuestionSubmitStatusEnum;
import com.abing.starry.model.request.question.QuestionPageRequest;
import com.abing.starry.model.request.question.QuestionSubmitRequest;
import com.abing.starry.model.vo.question.QuestionEntityVO;
import com.abing.starry.service.QuestionService;
import com.abing.starry.service.QuestionSubmitService;
import com.abing.starry.util.TokenUtils;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
* @author 阿炳亿点点帅
* @description 针对表【question(题目)】的数据库操作Service实现
* @createDate 2024-09-05 17:27:12
*/
@Service
@RequiredArgsConstructor
public class QuestionServiceImpl extends ServiceImpl<QuestionMapper, QuestionEntity>
    implements QuestionService{

    private final QuestionSubmitService questionSubmitService;

    private final CodeSandbox codeSandbox;

    @Override
    public IPage<QuestionEntity> listQuestionByPage(QuestionPageRequest questionPageRequest) {


        String title = questionPageRequest.getTitle();
        Integer status = questionPageRequest.getStatus();
        Integer level = questionPageRequest.getLevel();

        Page<QuestionEntity> questionEntityPage = this.page(new Page<>(questionPageRequest.getCurrent(), questionPageRequest.getPageSize()),
                                              Wrappers.lambdaQuery(QuestionEntity.class)
                                                      .like(StringUtils.isNotEmpty(title), QuestionEntity::getTitle, title)
                                                      .eq(Objects.nonNull(level), QuestionEntity::getTags, status));

        return questionEntityPage;
    }

    @Override
    public IPage<QuestionEntityVO> listQuestionForUserByPage(QuestionPageRequest questionPageRequest) {

        String userId = TokenUtils.getCurrentUser().getId();

        Page<QuestionEntity> questionEntityPage = this.page(
                new Page<>(questionPageRequest.getCurrent(), questionPageRequest.getPageSize()),
                Wrappers.lambdaQuery(QuestionEntity.class));

        List<QuestionEntity> questionEntityList = questionEntityPage.getRecords();

        List<String> questionIdList = questionEntityList.stream()
                                                        .map(QuestionEntity::getId)
                                                        .collect(Collectors.toList());

        List<QuestionSubmitEntity> questionSubmitEntityList = questionSubmitService.list(
                Wrappers.lambdaQuery(QuestionSubmitEntity.class)
                        .in(QuestionSubmitEntity::getQuestionId, questionIdList));

        Map<String, List<QuestionSubmitEntity>> currentUserSubmitEntityMap = questionSubmitEntityList
                .stream()
                .filter(questionSubmitEntity -> questionSubmitEntity.getUserId().equals(userId))
                .collect(Collectors.groupingBy(QuestionSubmitEntity::getQuestionId));

        List<QuestionEntityVO> questionEntityVOList = questionEntityList.stream()
                .map(questionEntity ->
                    questionEntityConverter(questionSubmitEntityList, currentUserSubmitEntityMap, questionEntity)
                ).collect(Collectors.toList());

        Page<QuestionEntityVO> resultPage = new Page<>();
        BeanUtils.copyProperties(questionEntityPage, resultPage);
        resultPage.setRecords(questionEntityVOList);

        return resultPage;
    }

    @NotNull
    private static QuestionEntityVO questionEntityConverter(List<QuestionSubmitEntity> questionSubmitEntityList, Map<String, List<QuestionSubmitEntity>> currentUserSubmitEntityMap, QuestionEntity questionEntity) {
        QuestionEntityVO questionEntityVO = new QuestionEntityVO();
        questionEntityVO.setId(questionEntity.getId());
        questionEntityVO.setTitle(questionEntity.getTitle());
        questionEntityVO.setLevel(questionEntity.getLevel());
        questionEntityVO.setAnswerCount(1);

        List<QuestionSubmitEntity> currentUserSubmitEntities = currentUserSubmitEntityMap.getOrDefault(questionEntity.getId(), new ArrayList<>());
        boolean isResolved = currentUserSubmitEntities
                .stream()
                .anyMatch(questionSubmitEntity -> QuestionSubmitStatusEnum.SUCCESS.getValue().equals(questionSubmitEntity.getStatus()));
        if (isResolved){
            questionEntityVO.setStatus(QuestionStatusEnum.RESOLVED.getValue());
        }
        if (!isResolved){
            questionEntityVO.setStatus(QuestionStatusEnum.TRIED.getValue());
        }
        if (currentUserSubmitEntities.isEmpty()){
            questionEntityVO.setStatus(QuestionStatusEnum.NOT_STARTED.getValue());
        }

        Map<String, List<QuestionSubmitEntity>> questionSubmitMap = questionSubmitEntityList
                .stream()
                .collect(Collectors.groupingBy(QuestionSubmitEntity::getQuestionId));

        Map<String, List<QuestionSubmitEntity>> userSubmitMap = questionSubmitMap
                .getOrDefault(questionEntity.getId(), new ArrayList<>())
                .stream()
                .collect(Collectors.groupingBy(QuestionSubmitEntity::getUserId));

        List<Boolean> passRateList = userSubmitMap
                .values()
                .stream()
                     .map(questionSubmitEntities ->
                                  questionSubmitEntities
                                          .stream()
                                          .anyMatch(record -> QuestionSubmitStatusEnum.SUCCESS.getValue()
                                                                                              .equals(record.getStatus()))
                     ).collect(Collectors.toList());

        long passedCount = passRateList.stream().filter(Boolean.TRUE::equals).count();
        if (!passRateList.isEmpty()){
            double passRate = (double) passedCount / passRateList.size();
            DecimalFormat df = new DecimalFormat("#.00");
            questionEntityVO.setPassRate(Float.parseFloat(df.format(passRate)));
        }
        return questionEntityVO;
    }


    @Override
    @RedissonRateLimit(key = "#questionSubmitRequest.userId")
    public QuestionSubmitEntity doQuestionSubmit(QuestionSubmitRequest questionSubmitRequest) {

        String questionId = questionSubmitRequest.getQuestionId();
        String language = questionSubmitRequest.getLanguage();
        String code = questionSubmitRequest.getCode();
        ThrowUtils.throwIf(StringUtils.isAnyEmpty(questionId, language, code), ErrorCode.PARAMS_ERROR, "参数不能为空");

        boolean languageAccepted = Arrays
                .stream(QuestionSubmitLanguageEnum.values())
                .anyMatch(e -> e.name().equals(language));

        ThrowUtils.throwIf(!languageAccepted, ErrorCode.PARAMS_ERROR,"语言类型不支持");

        String userId = questionSubmitRequest.getUserId();
        QuestionSubmitEntity questionSubmitEntity = new QuestionSubmitEntity();
        questionSubmitEntity.setUserId(userId);
        questionSubmitEntity.setQuestionId(questionSubmitRequest.getQuestionId());
        questionSubmitEntity.setLanguage(questionSubmitRequest.getLanguage());
        questionSubmitEntity.setCode(questionSubmitRequest.getCode());
        questionSubmitEntity.setStatus(QuestionStatusEnum.TRIED.getValue());
        boolean saveStatus = questionSubmitService.save(questionSubmitEntity);
        ThrowUtils.throwIf(!saveStatus, ErrorCode.SYSTEM_ERROR, "提交失败");

        // 执行判题逻辑
        QuestionEntity questionEntity = this.getById(questionId);
        ThrowUtils.throwIf(Objects.isNull(questionEntity),ErrorCode.NOT_FOUND_ERROR,"题目不存在");
        JudgeCase judgeCase = JSONUtil.toBean(questionEntity.getJudgeCase(), JudgeCase.class);
        List<String> inputs = judgeCase.getInputs();

        ExecuteCodeRequest executeCodeRequest = new ExecuteCodeRequest();
        executeCodeRequest.setInputs(inputs);
        executeCodeRequest.setLanguage(language);
        executeCodeRequest.setCode(code);
        ExecuteCodeResponse executeCodeResponse = codeSandbox.executeCode(executeCodeRequest);

        // 判题逻辑
        Integer status = executeCodeResponse.getStatus();
        // 代码级别的错误 编译和运行两种
        if (!ExecuteCodeStatusEnum.SUCCESS.getValue().equals(status)){
            return updateQuestionSubmit(questionSubmitEntity.getId(),
                                        executeCodeResponse,
                                        status,
                                        executeCodeResponse.getMessage());
        }

        // 输出超限
        List<String> outputList = executeCodeResponse.getOutputList();
        if (inputs.size() != outputList.size()){
            return updateQuestionSubmit(questionSubmitEntity.getId(),
                                        executeCodeResponse,
                                        QuestionSubmitStatusEnum.OUTPUT_OF_LIMIT.getValue(),
                                        QuestionSubmitStatusEnum.OUTPUT_OF_LIMIT.getText());
        }
        // 答案错误
        List<String> sampleOutput = judgeCase.getOutputs();
        for (int i = 0; i < sampleOutput.size(); i++) {

            if (!sampleOutput.get(i).equals(outputList.get(i))){
                return updateQuestionSubmit(questionSubmitEntity.getId(),
                                            executeCodeResponse,
                                            QuestionSubmitStatusEnum.ANSWER_INCORRECT.getValue(),
                                            QuestionSubmitStatusEnum.ANSWER_INCORRECT.getText());
            }

        }

        // 时间、内存超限
        List<JudgeInfo> judgeInfoList = executeCodeResponse.getJudgeInfo();
        JudgeConfig judgeConfig = JSONUtil.toBean(questionEntity.getJudgeConfig(), JudgeConfig.class);
        for (JudgeInfo judgeInfo : judgeInfoList) {

            if (judgeInfo.getMemory() > judgeConfig.getMemoryLimit()){
                return updateQuestionSubmit(questionSubmitEntity.getId(),
                                            executeCodeResponse,
                                            QuestionSubmitStatusEnum.MEMORY_OF_LIMIT.getValue(),
                                            QuestionSubmitStatusEnum.MEMORY_OF_LIMIT.getText());
            }

            if (judgeInfo.getTime() > judgeConfig.getTimeLimit()){
                return updateQuestionSubmit(questionSubmitEntity.getId(),
                                            executeCodeResponse,
                                            QuestionSubmitStatusEnum.TIME_OUT.getValue(),
                                            QuestionSubmitStatusEnum.TIME_OUT.getText());
            }

        }
        String message = executeCodeResponse.getMessage();

        updateQuestionSubmit(questionSubmitEntity.getId(), executeCodeResponse, status, message);
        return questionSubmitService.getById(questionSubmitEntity.getId());
    }

    private QuestionSubmitEntity updateQuestionSubmit(String questionSubmitId, ExecuteCodeResponse executeCodeResponse, Integer status, String message) {

        boolean updateStatus = questionSubmitService.update(
                Wrappers.lambdaUpdate(QuestionSubmitEntity.class)
                        .eq(QuestionSubmitEntity::getId, questionSubmitId)
                        .set(QuestionSubmitEntity::getJudgeInfo, JSONUtil.toJsonStr(executeCodeResponse.getJudgeInfo()))
                        .set(QuestionSubmitEntity::getStatus, status)
                        .set(QuestionSubmitEntity::getMessage, message));

        ThrowUtils.throwIf(!updateStatus, ErrorCode.SYSTEM_ERROR, "更新失败");
        return questionSubmitService.getById(questionSubmitId);
    }


}




